<?php 

class Quad_Date_Interval {
	protected $_years;
	protected $_months;
	protected $_weeks;
	protected $_days;
	protected $_hours;
	protected $_minutes;
	protected $_seconds;
	public $isAbsolute = true;
	public $invert;
	public $days;
	// the following are only set when instantiated with ISO 8601 durations spec (http://en.wikipedia.org/wiki/Iso8601#Durations)
	public $y;
	public $m;
	public $d;
	public $h;
	public $i;
	public $s;
	
	public static $multipliers = array(
		'weeks' => 604800,// 7 * 24 * 60 * 60,
		'days' => 86400, // 24 * 60 * 60
		'hours' => 3600, // 60 * 60
		'minutes' => 60,
		'seconds' => 1
	);
	
	public static function create($spec) {
		if ($spec instanceof self) {
			return $spec;
		}
		return new self($spec);
	}
	
	public function __construct($spec = null, $absolute = false) {
		$this->isAbsolute = $absolute;
		if (is_array($spec)) {
			$start = Quad_Date::create($spec[0]);
			$end = Quad_Date::create($spec[1]);
			
			list ($years, $months, $seconds) = self::_getDiffFromDates($start, $end);
//Quad_Debug::dump($years, $months);			
		}
		elseif (is_numeric($spec)) {
			$seconds = $spec;
			$years = $seconds / 365.2425;
			$months = $years / 12;
		}
		elseif (preg_match('/^P[.\d]+W|P[YMD\d.]+(T[HMS\d.]+)?$', $spec)) {
			// http://en.wikipedia.org/wiki/Iso8601#Durations
 			// regex matches the following:
			// P3Y6M4DT12H30M5S
			// P23DT23H
			// P4Y
			// P1M
			// PT1M
			// P0.5Y
			// PT36H
			// P1DT12H
			// P1Y2M10DT2H30M
			// P3WT1H
			// P3.5W
			preg_match('/^P([\d.]+Y)?([\d.]+M)?([\d.]+D)?(T([\d.]+H)?([\d.]+M)?([\d.]+S))?$/', $spec, $match);
			$this->y = float($match[1]);	
			$this->m = float($match[2]);	
			$this->d = float($match[3]);	
			$this->h = float($match[5]);	
			$this->i = float($match[6]);	
			$this->s = float($match[7]);	
			// validate
			// find seconds and derive years and months	
			
		}
		else {
			// strtotime type string
			$now = Date::create();
			$then = $d->add($spec);
			list ($years, $months, $seconds) = self::_getDiffFromDates($now, $then);
		}
		foreach (array('weeks','days','hours','minutes','seconds') as $unit) {
			$this->{"_$unit"} =  (float) ($seconds / self::$multipliers[$unit]);
		}
//Quad_Debug::dump((string) $start, (string) $end, $months, $years, $this);		
		$this->_years = (float) (($months / 12) * $years + ($this->_days / self::$multipliers['days']));
		$this->_months = (float) ($this->_years / 12);
		$this->invert = ($this->_seconds < 0.0);
		$this->days = $this->_days;
	}
	
	protected static function _getDiffFromDates(Quad_Date $d1, Quad_Date $d2) {
		$parts1 = explode(' ', $d1->format('Y n U'));
		$parts2 = explode(' ', $d2->format('Y n U'));				
		$years = (int) $parts2[0] - (int) $parts1[0];
		$months = (int) $parts2[1] - (int) $parts1[1];
		$seconds = (float) $parts2[2] - (float) $parts1[2];
//Quad_Debug::dumpdie((string)$d1, (string)$d2, $parts1, $parts2);
		if ($years < 0 && $parts2[1] > $parts1[1]) {
			$years += 1;
			$months = 12 - $months;
		}
		elseif ($years > 0 && $parts2[1] < $parts1[1]) {
			$years -= 1;
			$months += 12;
		}
		return array($years, $months, $seconds);		
	}
	
	/**
	 * 
	 * example usage:
	 *   Date::create('2008-09-10')->diff('2009-10-01')->inYears(); // 1
	 *   Date::create('2008-09-10')->diff('2009-10-01')->inYears(true); // 1.083
	 */
	public function __call($method, $args) {
		if (preg_match('/^in(Week|Day|Hour|Minute|Second)s$/', $method, $match)) {
			$dec = $this->{"_" . strtolower($match[1]) . 's'};
		}
		elseif ($method == 'inMonths') {
			$dec = $this->_months;
		}
		elseif ($method == 'inYears') {
			$dec = $this->_years;
		}
		else {
			throw new Quad_Exception("Undefined method `$method` in Quad_Date_Interval.");
		}
		if (count($args) == 0 || $args[0] == false) {
			$dec = self::floorTowardZero($dec);
		}
		if ($this->isAbsolute) {
			$dec = abs($dec);
		}
		return $dec;
	}
	
	public static function floorTowardZero($num) {
		if ($num < 0) {
			return ceil($num);
		}
		return floor($num);
	}
	
	public static $friendlyPhrases = array(
		'1 year' => 'next year',
		'-1 year' => 'last year',
		'N years' => 'in %s years',
		'-N years' => '%s years ago',
	
		'1 month' => 'next month',
		'-1 month' => 'last month',
		'N months' => 'in %s months',
		'-N months' => '%s months ago',
	
		'1 week' => 'next week',
		'-1 week' => 'last week',
		'N weeks' => 'in %s weeks',
		'-N weeks' => '%s weeks ago',
	
		'1 day' => 'tomorrow',
		'-1 day' => 'yesterday',
		'N days' => 'in %s days',
		'-N days' => '%s days ago',

		'1 hour' => 'in an hour',
		'-1 hour' => 'an hour ago',
		'N hours' => 'in %s hours',
		'-N hours' => '%s hours ago',

		'1 minute' => 'in 1 minute',
		'-1 minute' => '1 minute ago',
		'N minutes' => 'in %s minutes',
		'-N minutes' => '%s minutes ago',

		'moment' => 'in a moment',
		'-moment' => 'a moment ago',
	);	
	
	public function getFriendlyPhrase() {
		$replace = '';
		if (abs($this->_years) > 1) {
			$code = 'N years';
			$replace = $this->_years;
		}
		elseif (abs($this->_years) == 1) {
			$code = '1 year';
		}
		elseif (abs($this->_months) > 1) {
			$code = 'N months';
			$replace = $this->_months;
		}
		elseif (abs($this->_months) == 1) {
			$code = '1 month';
		}
		elseif (abs($this->_days) > 7) {
			$code = 'N weeks';
			$replace = floor($this->_days / 7);
		}
		elseif (abs($this->_days) == 7) {
			$code = '1 week';
		}		
		elseif (abs($this->_days) > 1) {
			$code = 'N days';
			$replace = $this->_days;
		}
		elseif (abs($this->_days) == 1) {
			$code = '1 day';
		}
		elseif (abs($this->_hours) > 1) {
			$code = 'N hours';
			$replace = $this->_hours;
		}
		elseif (abs($this->_hours) == 1) {
			$code = '1 hour';
		}
		elseif (abs($this->_minutes) > 1) {
			$code = 'N minutes';
			$replace = $this->_minutes;
		}
		elseif (abs($this->_minutes) == 1) {
			$code = '1 minute';
		}
		else {
			$code = 'moments';
		}
		if (!$this->isAbsolute && $seconds <= 0.0) {
			$code = '-' . $code;
		}
		return sprintf(self::$friendlyPhrases[$code], $replace);
	}
	
}